<?php
/***
 * Candy Zip处理类(包含流式打包下载)
 *
 * $Author: 刘森 (fingerboy@qq.com) $
 * $Date: 2022-8-2 15:10:15 $
 */
 
declare(strict_types=1);
namespace Candy\Extend;

defined('CANDY') OR die('You Are A Bad Guy. o_O???');

class Zip{
    //流式状态标识
    var $doWrite = false;
    
	//zip注释数组
    var $datasec = array();
    /**
     * Central directory
     *
     * @var  array $ctrl_dir
     */
    var $ctrl_dir = array();
    /**
     * End of central directory record
     *
     * @var  string $eof_ctrl_dir
     */
    var $eof_ctrl_dir = "\x50\x4b\x05\x06\x00\x00\x00\x00";
    
	//指针
    var $old_offset = 0;
    
	//zip文件描述
    var $comments='';
	
	
	/**
	 * 构造函数
	 * 
	 * @access public
	 * @return boolean
	 */
	public function __construct($options = [])
	{
		//可以传参后直接执行
		if(empty(!$options)){
			header("Content-Disposition: attachment; filename=". ($options['filename'] ?? date('YmdHis')) .".zip");
			
			//开启流式压缩
			if(isset($options['doWrite']) && $options['doWrite'] === true){
				$this->setDoWrite();
			}
			
			//添加压缩文件
			if(!empty($options['filelist'])){
				foreach ($options['filelist'] as $file){
					if($file['type'] == 'file'){
						$pathname = iconv('utf-8','gbk',$file['pathname']);
						$this->addFile(iconv('utf-8','gbk',file_get_contents($pathname)), $pathname);
						ob_flush();
						flush();
					}
				}
			}
			
			
			//设置注释,此处可以省略
			if(!empty($options['comments'])){
				$this->setComments(iconv('utf-8','gbk',$comments));
			}
			
			//输出文件尾部
			$this->file();
		}
	}
	
	//开启流打包模式
    function setDoWrite(){
        $this->doWrite = true;
    }
	
	//设置zip文件描述
    function setComments($comments){
        $this->comments=$comments;
    }
 
	//Unix时间戳转换DOS格式
    function unix2DosTime($unixtime = 0){
        $timearray = ($unixtime == 0) ? getdate() : getdate($unixtime);
 
        if ($timearray['year'] < 1980) {
            $timearray['year'] = 1980;
            $timearray['mon'] = 1;
            $timearray['mday'] = 1;
            $timearray['hours'] = 0;
            $timearray['minutes'] = 0;
            $timearray['seconds'] = 0;
        } // end if
 
        return (($timearray['year'] - 1980) << 25)
            | ($timearray['mon'] << 21)
            | ($timearray['mday'] << 16)
            | ($timearray['hours'] << 11)
            | ($timearray['minutes'] << 5)
            | ($timearray['seconds'] >> 1);
    }
	
	//添加文件到zip里
    function AddFile($data, $name, $time = 0){
        $name = str_replace('\\', '/', str_replace(CANDYROOT,'',$name));
        $hexdtime = pack('V', $this->unix2DosTime($time));
 
        $fr = "\x50\x4b\x03\x04";
        $fr .= "\x14\x00";
        $fr .= "\x00\x00";
        $fr .= "\x08\x00";
        $fr .= $hexdtime;
		
        $unc_len = strlen($data);
        $crc = crc32($data);
        $zdata = gzcompress($data);
        $zdata = substr(substr($zdata, 0, strlen($zdata) - 4), 2);
        $c_len = strlen($zdata);
        $fr .= pack('V', $crc);
        $fr .= pack('V', $c_len);
        $fr .= pack('V', $unc_len);
        $fr .= pack('v', strlen($name));
        $fr .= pack('v', 0);
        $fr .= $name;
		
        $fr .= $zdata;
		
        if ($this->doWrite) {
            echo $fr;
        } else {
            $this->datasec[] = $fr;
        }
		
        $cdrec = "\x50\x4b\x01\x02";
        $cdrec .= "\x00\x00";
        $cdrec .= "\x14\x00";
        $cdrec .= "\x00\x00";
        $cdrec .= "\x08\x00";
        $cdrec .= $hexdtime; 
        $cdrec .= pack('V', $crc);
        $cdrec .= pack('V', $c_len);
        $cdrec .= pack('V', $unc_len);
        $cdrec .= pack('v', strlen($name));
        $cdrec .= pack('v', 0);
        $cdrec .= pack('v', 0);
        $cdrec .= pack('v', 0);
        $cdrec .= pack('v', 0);
        $cdrec .= pack('V', 32);
        $cdrec .= pack('V', $this->old_offset);
        $this->old_offset += strlen($fr);
        $cdrec .= $name;
        $this->ctrl_dir[] = $cdrec;
    }
 
    //生成文件包或数据流
    function file(){
        $ctrldir = implode('', $this->ctrl_dir);
        $header = $ctrldir .
            $this->eof_ctrl_dir .
            pack('v', sizeof($this->ctrl_dir)) .
            pack('v', sizeof($this->ctrl_dir)) .
            pack('V', strlen($ctrldir)) . 
            pack('V', $this->old_offset) .
            pack('v',strlen($this->comments)).
            $this->comments;
 
        if ($this->doWrite) {
            echo $header;
            return "";
        } else {
            $data = implode('', $this->datasec);
            return $data . $header;
        }
    }
}
